package net.lulihu.common_util.jdbc.mapper;

import net.lulihu.Assert;
import net.lulihu.ObjectKit.CollectionKit;
import net.lulihu.common_util.jdbc.model.TemplateParam;
import net.lulihu.common_util.jdbc.template.SelectSqlTemplate;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;

/**
 * 构建数据库语句映射
 * <p>
 * 需要制定对象类型，指定的类型将返回值类型一致
 */
public class SpecifiedTypeMapper<T> extends DatabaseMapper {

    private final SelectSqlTemplate selectSqlTemplate;

    /**
     * 返回值类型
     */
    private final Class<T> resultClass;

    SpecifiedTypeMapper(Class<T> resultClass) {
        this.resultClass = resultClass;
        this.selectSqlTemplate = SelectSqlTemplate.getInstance();
    }

    /**
     * 查询指定条件的数据是否存在
     *
     * @param conditionalFields 查询条件字段名称   取值于对象的属性名称
     * @param values            查询参数值  必须与 conditionalField 长度顺序一致
     * @return 存在返回true反之false
     */
    public boolean contains(String[] conditionalFields, Object[] values) {
        Assert.isTrue(CollectionKit.isEmpty(conditionalFields), "条件属性不可以为空...");
        Assert.isTrue(conditionalFields.length != values.length, "条件参数与条件值长度不匹配...");
        List<T> result = getterList(conditionalFields, values);
        return CollectionKit.isNotEmpty(result);
    }

    /**
     * 根据主键id获取对应对象的映射值（单行全部列）
     *
     * @param pkId 主键值
     * @return 映射结果
     */
    @SuppressWarnings("unchecked")
    public T getterById(Object pkId) {
        Assert.notNull(pkId, "主键值不可以空...");
        TemplateParam templateParam = selectSqlTemplate.getterByIdSqlTemplate(resultClass, pkId);
        return (T) select(templateParam.getResultClass(), templateParam.getSqlStatement(), templateParam.getParams());
    }

    /**
     * 根据指定的条件查询映射的值（单行全部列）
     *
     * @param conditionalField 查询条件字段名称   取值于对象的属性名称
     * @param value            查询参数值  必须与 conditionalField 长度顺序一致
     * @return 映射结果
     */
    @SuppressWarnings("unchecked")
    public T getter(String conditionalField, Object value) {
        Assert.notNull(conditionalField, "条件属性不可以为空...");
        Assert.notNull(value, "条件值不可以为空...");
        TemplateParam templateParam = selectSqlTemplate.getterSqlTemplate(this.resultClass, new String[]{conditionalField}, new Object[]{value});
        return (T) select(templateParam.getResultClass(), templateParam.getSqlStatement(), templateParam.getParams());
    }

    /**
     * 根据指定的条件查询映射的值（单行全部列）
     *
     * @param conditionalFields 查询条件字段名称   取值于对象的属性名称
     * @param values            查询参数值  必须与 conditionalField 长度顺序一致
     * @return 映射结果
     */
    @SuppressWarnings("unchecked")
    public T getter(String[] conditionalFields, Object[] values) {
        TemplateParam templateParam = getterTemplateParam(conditionalFields, values);
        return (T) select(templateParam.getResultClass(), templateParam.getSqlStatement(), templateParam.getParams());
    }

    /**
     * 根据指定的条件查询映射的值(多行全部列)
     *
     * @param conditionalFields 查询条件字段名称   取值于对象的属性名称
     * @param values            查询参数值  必须与 conditionalField 长度顺序一致
     * @return 映射结果
     */
    public List<T> getterList(String[] conditionalFields, Object[] values) {
        TemplateParam templateParam = getterTemplateParam(conditionalFields, values);
        return selectList(templateParam.getSqlStatement(), templateParam.getParams());
    }

    /**
     * 获取全部列的模板参数
     *
     * @param conditionalFields 查询条件字段名称   取值于对象的属性名称
     * @param values            查询参数值  必须与 conditionalField 长度顺序一致
     * @return TemplateParam
     */
    private TemplateParam getterTemplateParam(String[] conditionalFields, Object[] values) {
        Assert.isTrue(CollectionKit.isEmpty(conditionalFields), "条件属性不可以为空...");
        Assert.isTrue(conditionalFields.length != values.length, "条件参数与条件值长度不匹配...");
        return getSelectSqlTemplate().getterSqlTemplate(getResultClass(), conditionalFields, values);
    }

    /**
     * 根据主键id获取单行单列属性值
     *
     * @param fieldName 映射属性名称
     * @param pkId      对应数据主键值
     * @param <D>       泛型接受
     * @return 数据库对应属性值
     */
    @SuppressWarnings("unchecked")
    public <D> D getColumnValueById(String fieldName, Object pkId) {
        Assert.notNull(fieldName, "查询属性不可以为空...");
        Assert.notNull(pkId, "主键值不可以为空...");
        TemplateParam templateParam = getSelectSqlTemplate().getColumnValueByIdSqlTemplate(getResultClass(), fieldName, pkId);
        return (D) select(templateParam.getResultClass(), templateParam.getSqlStatement(), templateParam.getParams());
    }


    /**
     * 根据主键id获取单行多列的值
     * <p>
     * 多个属性值将被封装到指定类型对象当中
     *
     * @param fieldNames 多个属性名称
     * @param pkId       主键id
     * @return 封装的对象
     */
    @SuppressWarnings("unchecked")
    public T getColumnsById(String[] fieldNames, Object pkId) {
        Assert.isTrue(CollectionKit.isEmpty(fieldNames), "查询属性不可以为空...");
        Assert.notNull(pkId, "主键值不可以为空...");
        TemplateParam templateParam = getSelectSqlTemplate().getColumnsByIdSqlTemplate(getResultClass(), fieldNames, pkId);
        return (T) select(templateParam.getResultClass(), templateParam.getSqlStatement(), templateParam.getParams());
    }

    /**
     * 获取单行多列属性值
     * <p>
     * 多个属性值将被封装到指定类型对象当中
     *
     * @param queryField       查询结果字段名称  取值于对象的属性名称
     * @param conditionalField 查询条件字段名称   取值于对象的属性名称
     * @param values           查询参数值  必须与 conditionalField 长度顺序一致
     * @return 封装的对象
     */
    @SuppressWarnings("unchecked")
    public T getColumnValues(String[] queryField, String[] conditionalField, Object[] values) {
        TemplateParam templateParam = getColumnValuesTemplateParam(queryField, conditionalField, values);
        return (T) select(templateParam.getResultClass(), templateParam.getSqlStatement(), templateParam.getParams());
    }

    /**
     * 获取多行多列属性值
     * <p>
     * 多个属性值将被封装到指定类型对象当中
     *
     * @param queryField       查询结果字段名称  取值于对象的属性名称
     * @param conditionalField 查询条件字段名称   取值于对象的属性名称
     * @param values           查询参数值  必须与 conditionalField 长度顺序一致
     * @return 封装的对象
     */
    public List<T> getListColumnValues(String[] queryField, String[] conditionalField, Object[] values) {
        TemplateParam templateParam = getColumnValuesTemplateParam(queryField, conditionalField, values);
        return selectList(templateParam.getSqlStatement(), templateParam.getParams());
    }

    /**
     * 获取多列值参数模板
     *
     * @param queryField       查询结果字段名称  取值于对象的属性名称
     * @param conditionalField 查询条件字段名称   取值于对象的属性名称
     * @param values           查询参数值  必须与 conditionalField 长度顺序一致
     * @return 封装的对象 TemplateParam
     */
    private TemplateParam getColumnValuesTemplateParam(String[] queryField, String[] conditionalField, Object[] values) {
        Assert.isTrue(CollectionKit.isEmpty(queryField), "查询属性不可以为空...");
        Assert.isTrue(CollectionKit.isEmpty(conditionalField), "条件属性不可以为空...");
        Assert.isTrue(conditionalField.length != values.length, "条件参数与条件值长度不匹配...");
        return getSelectSqlTemplate().getColumnsSqlTemplate(getResultClass(), queryField, conditionalField, values);
    }

    /**
     * 获取单行单列属性值
     *
     * @param fieldName        查询结果字段名称  取值于对象的属性名称
     * @param conditionalField 查询条件字段名称   取值于对象的属性名称
     * @param value            查询参数值
     * @return 封装的对象
     */
    public <D> D getSingleColumnValues(String fieldName, String conditionalField, Object value) {
        return getSingleColumnValues(fieldName, new String[]{conditionalField}, new Object[]{value});
    }

    /**
     * 获取单行单列属性值
     *
     * @param fieldName         查询结果字段名称  取值于对象的属性名称
     * @param conditionalFields 查询条件字段名称   取值于对象的属性名称
     * @param values            查询参数值  必须与 conditionalField 长度顺序一致
     * @return 封装的对象
     */
    @SuppressWarnings("unchecked")
    public <D> D getSingleColumnValues(String fieldName, String[] conditionalFields, Object[] values) {
        TemplateParam templateParam = getSingleColumnValuesTemplateParam(fieldName, conditionalFields, values);
        return (D) select(templateParam.getResultClass(), templateParam.getSqlStatement(), templateParam.getParams());
    }

    /**
     * 获取多行单列属性值
     *
     * @param fieldName         查询结果字段名称  取值于对象的属性名称
     * @param conditionalFields 查询条件字段名称   取值于对象的属性名称
     * @param values            查询参数值  必须与 conditionalField 长度顺序一致
     * @return 封装的对象
     */
    @SuppressWarnings("unchecked")
    public <D> List<D> getListSingleColumnValues(String fieldName, String[] conditionalFields, Object[] values) {
        TemplateParam templateParam = getSingleColumnValuesTemplateParam(fieldName, conditionalFields, values);
        return (List<D>) selectList(ArrayList.class, templateParam.getResultClass(), templateParam.getSqlStatement(), templateParam.getParams());
    }

    /**
     * 获取单例属性参数模板
     *
     * @param fieldName        查询结果字段名称  取值于对象的属性名称
     * @param conditionalField 查询条件字段名称   取值于对象的属性名称
     * @param values           查询参数值  必须与 conditionalField 长度顺序一致
     * @return TemplateParam
     */
    private TemplateParam getSingleColumnValuesTemplateParam(String fieldName, String[] conditionalField, Object[] values) {
        Assert.notNull(fieldName, "查询属性不可以为空...");
        Assert.isTrue(CollectionKit.isEmpty(conditionalField), "条件属性不可以为空...");
        Assert.isTrue(conditionalField.length != values.length, "条件参数与条件值长度不匹配...");
        return selectSqlTemplate.getSingleColumnSqlTemplate(getResultClass(), fieldName, conditionalField, values);
    }

    /**
     * 执行查询语句，只返回一条结果
     *
     * @param sqlStatement sql命令 这边的占位符使用 ?
     * @param params       占位符替换参数，将按照顺序替换sql语句的 ?
     * @return 查询结果
     */
    public T select(String sqlStatement, Object... params) {
        return select(getResultClass(), sqlStatement, params);
    }

    /**
     * 执行查询语句
     *
     * @param resultClass  返回值类型
     * @param sqlStatement sql命令 这边的占位符使用 ?
     * @param params       占位符替换参数，将按照顺序替换sql语句的 ?
     * @param <D>          返回值类型
     * @return 查询结果
     */
    private <D> D select(Class<D> resultClass, String sqlStatement, Object... params) {
        return execute((connection) -> {
            Map<String, String> propertyMapping = getSelectSqlTemplate().getPropertyMapping(resultClass);
            return getSqlStatementExecutor().executeQueryOne(connection, resultClass, propertyMapping, sqlStatement, params);
        });
    }

    /**
     * 执行查询语句，返回多条结果
     * <p>
     * 多条结果封装的集合为 {@linkplain ArrayList} 如果需要自定义封装集合对象请参考重载方法
     *
     * @param sqlStatement sql命令 这边的占位符使用 ?
     * @param params       占位符替换参数，将按照顺序替换sql语句的 ?
     * @return 查询结果
     */
    public List<T> selectList(String sqlStatement, Object... params) {
        return selectList(ArrayList.class, sqlStatement, params);
    }

    /**
     * 执行查询语句，返回多条结果
     *
     * @param resultListClass 指定封装集合类型
     * @param sqlStatement    sql命令 这边的占位符使用 ?
     * @param params          占位符替换参数，将按照顺序替换sql语句的 ?
     * @return 查询结果
     */
    public List<T> selectList(Class<? extends List> resultListClass, String sqlStatement, Object... params) {
        return selectList(resultListClass, getResultClass(), sqlStatement, params);
    }

    /**
     * 执行查询语句，返回多条结果
     *
     * @param resultListClass 指定封装集合类型
     * @param sqlStatement    sql命令 这边的占位符使用 ?
     * @param params          占位符替换参数，将按照顺序替换sql语句的 ?
     * @return 查询结果
     */
    private <D> List<D> selectList(Class<? extends List> resultListClass, Class<D> resultClass, String sqlStatement, Object... params) {
        return execute((connection) -> {
            Map<String, String> propertyMapping = getSelectSqlTemplate().getPropertyMapping(resultClass);
            return getSqlStatementExecutor().executeQueryList(connection, resultListClass, resultClass, propertyMapping, sqlStatement, params);
        });
    }


    private SelectSqlTemplate getSelectSqlTemplate() {
        return selectSqlTemplate;
    }

    private Class<T> getResultClass() {
        return this.resultClass;
    }
}
